package system.task;

import framework.defined.SystemDefined;
import framework.storage.FileStorage;
import framework.utils.JsonUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.*;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import system.service.sys.SysTaskDetailService;
import system.service.sys.SysTaskLogService;
import system.task.config.TaskConfig;
import system.task.defined.TaskStatus;
import system.task.errors.TaskException;
import system.task.listener.ScheListener;
import system.task.listener.TaskListener;
import system.task.listener.TrigListener;
import system.task.quartz.JobProcessor;
import system.task.utils.TaskUtil;

import java.util.*;

/**
 * 任务管理器
 */
@Slf4j
@Component
public class TaskManagerQuartZ implements TaskManager {

    private Scheduler scheduler;
    private TaskConfig taskConfig;

    public TaskManagerQuartZ(Scheduler scheduler,
                             TaskConfig taskConfig,
                             SysTaskLogService logService,
                             SysTaskDetailService detailService,
                             FileStorage fileStorage,
                             ApplicationEventPublisher eventPublisher) {
        this.scheduler = scheduler;
        this.taskConfig = taskConfig;
        try {
            ListenerManager listenerManager = scheduler.getListenerManager();
            //job listener
            listenerManager.addJobListener(new TaskListener(logService,
                    detailService,
                    taskConfig,
                    fileStorage,
                    eventPublisher));
            //scheduler listener
            listenerManager.addSchedulerListener(new ScheListener(this, eventPublisher));
            //trigger listener
            listenerManager.addTriggerListener(new TrigListener());
        } catch (SchedulerException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void start() throws TaskException {
        try {
            scheduler.start();
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }
    }

    @Override
    public void stop() throws TaskException {
        try {
            scheduler.shutdown();
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }
    }

    @Override
    public void pause() throws TaskException {
        try {
            scheduler.pauseAll();
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }
    }

    @Override
    public void resume() throws TaskException {
        try {
            scheduler.resumeAll();
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }
    }

    @Override
    public Task getTask(String name) throws TaskException {
        JobKey jobKey = new JobKey(name, TaskUtil.GROUP_NAME);

        try {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail == null) return null;
            return (Task) jobDetail.getJobDataMap().get(TaskUtil.TASK_TASK_NAME);
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }
    }

    @Override
    public synchronized boolean addTask(Task task) throws TaskException {
        String actuator = task.getActuator();
        if (!StringUtils.hasText(actuator)) {
            throw new TaskException("error actuator " + actuator + " by " + task.getName());
        }

        if (!StringUtils.hasText(task.getName())) {
            throw new TaskException("task name is empty");
        }

        //
        JobKey jobKey = new JobKey(task.getName(), TaskUtil.GROUP_NAME);
        TriggerKey triggerKey = new TriggerKey(task.getName(), TaskUtil.GROUP_NAME);

        //存在判定
        try {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail != null) {

                Task oldTask = (Task) jobDetail.getJobDataMap().get(TaskUtil.TASK_TASK_NAME);

                int isChanged = 0;

                //参数覆盖
                if (!Objects.equals(task.getParam(), oldTask.getParam())) {
                    isChanged = 1;
                }
                //对已存在的任务进行trigger编辑
                else if (!Objects.equals(task.getSchedule(), oldTask.getSchedule())) {
                    isChanged = 2;
                }
                //状态变化
                else if (!Objects.equals(task.getStatus(), oldTask.getStatus())) {
                    isChanged = 3;
                }
                //开始时间变化
                else if (!Objects.equals(task.getStartTime(), oldTask.getStartTime())) {
                    isChanged = 4;
                }
                //终止时间变化
                else if (!Objects.equals(task.getExpireTime(), oldTask.getExpireTime())) {
                    isChanged = 5;
                }
                //执行器
                else if (!Objects.equals(task.getActuator(), oldTask.getActuator())) {
                    isChanged = 6;
                }
                //策略
                else if (!Objects.equals(task.getInstruction(), oldTask.getInstruction())) {
                    isChanged = 7;
                }


                //无变化
                if (isChanged == 0) {
                    // 余下变化但不影响任务的参数赋值
                    oldTask.setGroupName(task.getGroupName());
                    oldTask.setCreateTime(task.getCreateTime());
                    //
                    if (log.isInfoEnabled()) {
                        log.info("Task " + jobKey.getName() + " no change");
                    }
                    //
                    return false;
                }

                //有变化，修改
                //删除
                scheduler.unscheduleJob(triggerKey);
                scheduler.deleteJob(jobKey);
            }
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }

        //
        JobDetail jobDetail = JobBuilder.newJob(JobProcessor.class)
                .withIdentity(jobKey)
                .storeDurably()
                .build();

        // add task info
        jobDetail.getJobDataMap().put(TaskUtil.TASK_TASK_NAME, task);

        // param
        this.parseParam(jobDetail, task);

        //
        Trigger trigger;
        try {
            trigger = this.parseTrigger(task, triggerKey);
        } catch (Exception exception) {
            log.warn("Task " + jobKey.getName() + " trigger value " + task.getSchedule() + " invalid");
            trigger = null;
        }

        //
        if (log.isInfoEnabled()) {
            log.info("Task add " + jobKey.getName());
        }

        //
        try {
            if (SystemDefined.YES_VALUE.equals(this.taskConfig.getTaskDisableTrigger())) {
                log.warn("Task " + jobKey.getName() + " trigger disabled because config task-disable-trigger = 1");
                scheduler.addJob(jobDetail, false);
            } else if (trigger != null && TaskStatus.ENABLED.equals(task.getStatus())) {
                scheduler.scheduleJob(jobDetail, trigger);
            } else {
                scheduler.addJob(jobDetail, false);
            }
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }

        //
        return true;
    }

    @Override
    public List<Date> getTriggerTimes(String name, int size, Date afterTime) throws TaskException {
        if (afterTime == null)
            throw new IllegalArgumentException("afterTime is null");
        JobKey jobKey = new JobKey(name, TaskUtil.GROUP_NAME);
        List<Date> timeList = new ArrayList<>();

        //
        try {
            Date time = afterTime;
            List<? extends Trigger> triggersOfJob = scheduler.getTriggersOfJob(jobKey);
            for (Trigger trigger : triggersOfJob) {
                for (int i = 0; i < size; i++) {
                    time = trigger.getFireTimeAfter(time);
                    if (time == null) {
                        break;
                    }
                    timeList.add(time);
                }
            }
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }

        // sort
        timeList.sort((a, b) -> {
            return a.compareTo(b);
        });

        //
        if (timeList.size() > size) {
            return timeList.subList(0, size);
        }

        //
        return timeList;
    }

    private void parseParam(JobDetail jobDetail, Task task) {
        JobDataMap dataMap = jobDetail.getJobDataMap();
        String param = task.getParam();
        if (StringUtils.hasText(param)) {
            HashMap map = JsonUtil.parse(param, HashMap.class);
            dataMap.putAll(map);
        }
    }

    private Trigger parseTrigger(Task task, TriggerKey triggerKey) throws TaskException {
        Trigger trigger = TaskUtil.parseTrigger(task, triggerKey);
        //
        if (trigger != null) {
            JobDataMap dataMap = trigger.getJobDataMap();
            dataMap.put(TaskUtil.TASK_RULE_NAME, task.getSchedule());
        }
        //
        return trigger;
    }

    @Override
    public synchronized void delTask(String name) throws TaskException {
        //停止
        this.stopTask(name);
        //删除
        try {
            JobKey jobKey = new JobKey(name, TaskUtil.GROUP_NAME);
            if (log.isInfoEnabled()) {
                log.info("START TASK " + jobKey.getName());
            }
            if (scheduler.getJobDetail(jobKey) != null) {
                scheduler.deleteJob(jobKey);
            }
        } catch (SchedulerException e) {
            throw new TaskException(e);
        }
    }

    @Override
    public synchronized void startTask(String name, Map param) throws TaskException {
        JobKey jobKey = new JobKey(name, TaskUtil.GROUP_NAME);
        if (log.isInfoEnabled()) {
            log.info("START TASK " + jobKey.getName());
        }

        try {
            JobDetail jobDetail = scheduler.getJobDetail(jobKey);
            if (jobDetail == null) {
                throw new TaskException("Not found task " + name);
            }
            //
            JobDataMap dataMap = new JobDataMap();
            dataMap.putAll(jobDetail.getJobDataMap());
            for (Object o : param.keySet()) {
                dataMap.put(String.valueOf(o), param.get(o));
            }
            // add manual flag
            dataMap.put(TaskUtil.TASK_TRIGGER_MANUAL, "1");
            //
            scheduler.triggerJob(jobKey, dataMap);
        } catch (SchedulerException e) {
            throw new TaskException("start failed " + e.getMessage(), e);
        }
    }

    @Override
    public synchronized void stopTask(String name) throws TaskException {
        JobKey jobKey = new JobKey(name, TaskUtil.GROUP_NAME);
        try {
            // 获取正在执行的任务
            List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
            // 遍历任务
            for (JobExecutionContext executingJob : executingJobs) {
                // 找到，则终止该任务的执行
                if (jobKey.equals(executingJob.getJobDetail().getKey())) {
                    if (log.isInfoEnabled()) {
                        log.info("STOP TASK " + jobKey.getName());
                    }
                    try {
                        scheduler.interrupt(jobKey);
                    } catch (UnableToInterruptJobException exception) {
                        throw new TaskException("STOP fail " + exception.getMessage(), exception);
                    }
                }
            }
        } catch (SchedulerException e) {
            throw new TaskException("stop failed " + e.getMessage(), e);
        }
    }

    @SneakyThrows
    @Override
    public synchronized void cleanNoContain(Set<String> nameSet) {

        Set<JobKey> jobKeys = scheduler.getJobKeys(GroupMatcher.anyGroup());
        for (JobKey k : jobKeys) {

            //当任务不存在于传入的列表中时, 删除
            if (!nameSet.contains(k.getName())) {
                scheduler.deleteJob(k);
                if (log.isInfoEnabled()) {
                    log.info("DELETE TASK " + k.getName());
                }
            }

        }

    }

}
